package com.laiyuezs.sdk2x;


import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.TransactionException;
import org.hyperledger.fabric.sdk.security.CryptoSuite;

import java.io.*;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.Security;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 构建网络管理对象，主要是构建 HFClient 和 Channel
 *
 * @author : 州长在手 2021/4/1 上午11:19
 */
@Slf4j
@Data
public class OrgManage {

    private final String cryptoConfig;
    private final String domainName;
    private HFClient hfClient;
    private Channel channel;

    public OrgManage(String cryptoConfig, String domainName) {
        this.cryptoConfig = cryptoConfig;
        this.domainName = domainName;
    }


    public OrgManage initHFClient() {
        try {
            HFClient client = HFClient.createNewInstance();
            client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
            this.hfClient = client;
            return this;
        } catch (Exception e) {
            log.error("创建HFClient错误： \n" + e.getMessage());

            return null;
        }
    }

    public OrgManage hfClientAddUser(UserContext userContext) {
        hfClient.setUserContext(userContext);
        return this;
    }

    public OrgManage initChannel(String channelName) {
        try {
            this.channel = hfClient.newChannel(channelName);
            return this;
        } catch (InvalidArgumentException e) {
            log.error("创建通道错误：  \n" + e.getMessage());
            return null;
        }
    }

    public OrgManage channelAddOrderer(Orderer orderer) {
        try {
            channel.addOrderer(orderer);
            return this;
        } catch (InvalidArgumentException e) {
            log.error("通道添加orderer错误： " + e.getMessage());
            return null;
        }
    }

    public OrgManage channelAddPeer(Peer peer) {
        try {
            channel.addPeer(peer);
            return this;
        } catch (InvalidArgumentException e) {
            log.error("通道添加peer错误： " + e.getMessage());
            return null;
        }
    }

    public void build() {
        if (!channel.isInitialized()) {
            try {
                channel.initialize();
            } catch (InvalidArgumentException | TransactionException e) {
                log.error("channel initialize error  " + e.getMessage());
            }
        }
    }

    public Orderer newOrderer(String name) {
        Properties rt = FactoryUtil.getOrdererProperties(name, domainName, cryptoConfig);
        try {
            return hfClient.newOrderer(name, "grpcs://" + name + "." + domainName + ":7050", rt);
        } catch (InvalidArgumentException e) {
            log.error("创建 new Orderer Error " + e.getMessage());
            return null;
        }
    }

    // 主要是为了方便 docker
    public Orderer newOrderer(String name, String ip, String port) {

        Properties rt = FactoryUtil.getOrdererProperties(name, domainName, cryptoConfig);
        try {

          //  System.out.println("grpcs://" + ip + ":" + port);
            return hfClient.newOrderer(name, "grpcs://" + ip + ":" + port, rt);
        } catch (InvalidArgumentException e) {
            log.error("创建 new Orderer Error " + e.getMessage());
            return null;
        }
    }

    public Peer newPeer(String name, String orgName) {
        Properties rt = FactoryUtil.getPeerProperties(name, orgName, domainName, cryptoConfig);
        try {
            return hfClient.newPeer(name, "grpcs://" + name + "." + orgName + "." + domainName + ":7051", rt);
        } catch (InvalidArgumentException e) {
            log.error("创建 new Peer Error " + e.getMessage());
            return null;
        }
    }

    // 主要是为了方便 docker
    public Peer newPeer(String name, String orgName, String ip, String port) {
        Properties rt = FactoryUtil.getPeerProperties(name, orgName, domainName, cryptoConfig);
        try {
            return hfClient.newPeer(name, "grpcs://" + ip + ":" + port, rt);
        } catch (InvalidArgumentException e) {
            log.error("创建 new Peer Error " + e.getMessage());
            return null;
        }
    }


    public UserContext newUser(String name, String mspId, String orgName) {

        try {
            return FactoryUtil.getUserContext(name, mspId, orgName, domainName, cryptoConfig);
        } catch (IOException e) {
            log.error("创建 new User Error " + e.getMessage());
            return null;
        }
    }

    public UserContext newUser(String name, String mspId) {

        try {
            return FactoryUtil.getUserContext(name, mspId, domainName, cryptoConfig);
        } catch (IOException e) {
            log.error("创建 new User Error " + e.getMessage());
            return null;
        }
    }

    //----------------------------------------------------- Factory Util   -----------------------------------------------------

    public static class FactoryUtil {

        static {
            try {
                Security.addProvider(new BouncyCastleProvider());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public static Properties getOrdererProperties(final String name, final String domainName, final String cryptoConfigPath) {
            Properties ret = new Properties();
            File cert = Paths.get(cryptoConfigPath, "crypto-config/ordererOrganizations", domainName, "orderers",
                    // orderer.example.com-cert.pem
                    //           name + "." + domainName, "msp/signcerts/",name+"."+domainName+"-cert.pem").toFile();
                    name + "." + domainName, "tls/server.crt").toFile();
            if (!cert.exists()) {
                throw new RuntimeException(String.format("Missing cert file for: %s. Could not find at location: %s", name,
                        cert.getAbsolutePath()));
            }
/*          不受影响
            File clientCert;
            File clientKey;
            clientCert = Paths.get(cryptoConfigPath, "crypto-config/ordererOrganizations/", domainName, "users", "Admin@" + domainName, "tls", "client.crt").toFile();
            clientKey = Paths.get(cryptoConfigPath, "crypto-config/ordererOrganizations/", domainName, "users", "Admin@" + domainName, "tls", "client.key").toFile();
            if (!clientCert.exists()) {
                throw new RuntimeException(String.format("Missing  client cert file for: %s. Could not find at location: %s", name,
                        clientCert.getAbsolutePath()));
            }
            if (!clientKey.exists()) {
                throw new RuntimeException(String.format("Missing  client key file for: %s. Could not find at location: %s", name,
                        clientKey.getAbsolutePath()));
            }*/
            //    ret.setProperty("clientCertFile", clientCert.getAbsolutePath());
            //  ret.setProperty("clientKeyFile", clientKey.getAbsolutePath());
            ret.setProperty("pemFile", cert.getAbsolutePath());
            ret.setProperty("hostnameOverride", name + "." + domainName);
            ret.setProperty("sslProvider", "openSSL");
            ret.setProperty("negotiationType", "TLS");

            // 设置keepAlive以避免在不活跃的http2连接上超时的例子。在5分钟内，需要对服务器端进行更改，以接受更快的ping速率。
            ret.put("grpc.NettyChannelBuilderOption.keepAliveTime", new Object[]{5L, TimeUnit.MINUTES});
            ret.put("grpc.NettyChannelBuilderOption.keepAliveTimeout", new Object[]{8L, TimeUnit.SECONDS});
            ret.put("grpc.NettyChannelBuilderOption.keepAliveWithoutCalls", new Object[]{true});
            return ret;
        }

        public static Properties getPeerProperties(final String name, final String orgName, final String domainName, final String cryptoConfigPath) {
            Properties ret = new Properties();
            File cert = Paths.get(cryptoConfigPath, "crypto-config/peerOrganizations", orgName + "." + domainName, "peers",
                    //      name + "." + orgName + "." + domainName, "tls/server.crt").toFile();
                    name + "." + orgName + "." + domainName, "tls/server.crt").toFile();
            if (!cert.exists()) {
                throw new RuntimeException(String.format("Missing cert file for: %s. Could not find at location: %s", name,
                        cert.getAbsolutePath()));
            }

 /*   不受影响
            File clientCert;
            File clientKey;

           // clientCert = Paths.get(cryptoConfigPath, "crypto-config/peerOrganizations", orgName + "." + domainName, "users/Admin@" + orgName + "." + domainName, "tls/client.crt").toFile();
          //  clientKey = Paths.get(cryptoConfigPath, "crypto-config/peerOrganizations", orgName + "." + domainName, "users/Admin@" + orgName + "." + domainName, "tls/client.key").toFile();

//            if (!clientCert.exists()) {
//                throw new RuntimeException(String.format("Missing  client cert file for: %s. Could not find at location: %s", name,
//                        clientCert.getAbsolutePath()));
//            }
//
//            if (!clientKey.exists()) {
//                throw new RuntimeException(String.format("Missing  client key file for: %s. Could not find at location: %s", name,
//                        clientKey.getAbsolutePath()));
//            }

 */
            //     ret.setProperty("clientCertFile", clientCert.getAbsolutePath());
            //      ret.setProperty("clientKeyFile", clientKey.getAbsolutePath());


            ret.setProperty("pemFile", cert.getAbsolutePath());

            ret.setProperty("hostnameOverride", name + "." + orgName + "." + domainName);
            ret.setProperty("sslProvider", "openSSL");
            ret.setProperty("negotiationType", "TLS");

            //    在grpc的NettyChannelBuilder上设置特定选项
            ret.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 9000000);
            return ret;
        }

        public static PrivateKey getPrivateKeyFromBytes(String data) throws IOException {
            final Reader pemReader = new StringReader(data);
            final PrivateKeyInfo pemPair;
            try (PEMParser pemParser = new PEMParser(pemReader)) {
                pemPair = (PrivateKeyInfo) pemParser.readObject();
            }
            return new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getPrivateKey(pemPair);

        }

        public static UserContext getUserContext(final String name, final String mspId, final String orgName, final String domainName, final String cryptoConfigPath) throws IOException {
            File cert = Paths.get(cryptoConfigPath, "crypto-config/peerOrganizations", orgName + "." + domainName, "users",
                    name + "@" + orgName + "." + domainName, "msp/signcerts", name + "@" + orgName + "." + domainName + "-cert.pem").toFile();
            File dirs = Paths.get(cryptoConfigPath, "crypto-config/peerOrganizations", orgName + "." + domainName, "users",
                    name + "@" + orgName + "." + domainName, "msp/keystore").toFile();
            String certificate = new String(IOUtils.toByteArray(new FileInputStream(cert)), "UTF-8");
            String skString = new String(IOUtils.toByteArray(new FileInputStream(dirs.listFiles()[0])), "UTF-8");
            PrivateKey privateKey = getPrivateKeyFromBytes(skString);
            return new UserContext(name, mspId, privateKey, certificate);
        }

        // 获取 Orderer Admin 账户 OrdererMSP
        public static UserContext getUserContext(final String name, final String mspId, final String domainName, final String cryptoConfigPath) throws IOException {
            File cert = Paths.get(cryptoConfigPath, "crypto-config/ordererOrganizations",  domainName, "users",
                    name + "@"  + domainName, "msp/signcerts", name + "@" + domainName + "-cert.pem").toFile();
            File dirs = Paths.get(cryptoConfigPath, "crypto-config/ordererOrganizations",  domainName, "users",
                    name + "@" +   domainName, "msp/keystore").toFile();
            String certificate = new String(IOUtils.toByteArray(new FileInputStream(cert)), "UTF-8");
            String skString = new String(IOUtils.toByteArray(new FileInputStream(dirs.listFiles()[0])), "UTF-8");
            PrivateKey privateKey = getPrivateKeyFromBytes(skString);
            return new UserContext(name, mspId, privateKey, certificate);
        }

    }

    //----------------------------------------------------- User Context   -----------------------------------------------------
    public static class UserContext implements User {
        //private static final long serialVersionUID = 6965341351799577442L;
        private final String name;
        private final String mspId;
        private final PrivateKey privateKey;
        private final String cert;

        public UserContext(String name, String mspId, PrivateKey privateKey, String cert) {
            this.name = name;
            this.mspId = mspId;
            this.privateKey = privateKey;
            this.cert = cert;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public Set<String> getRoles() {
//           Set<String> set =new HashSet<>();
//           set.add("Admins");
//            return set;
            return null;
        }

        @Override
        public String getAccount() {
            return null;
        }

        @Override
        public String getAffiliation() {
            return null;
        }

        @Override
        public Enrollment getEnrollment() {
            return new Enrollment() {
                @Override
                public PrivateKey getKey() {
                    return privateKey;
                }

                @Override
                public String getCert() {
                    return cert;
                }
            };
        }

        @Override
        public String getMspId() {
            return mspId;
        }
    }

    // -------------------------------------------------- 功能函数 -----------------------------------------------------


}

